一、概述
ZUUL是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
动态路由:动态将请求路由到不同后端集群
压力测试:逐渐增加指向集群的流量,以了解性能
负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
静态响应处理:边缘位置进行响应,避免转发到内部集群
身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求。
Spring Cloud对Zuul进行了整合和增强。
服务网关是在微服务前边设置一道屏障,请求先到服务网关,网关会对请求进行过滤、校验、路由、代理等处理。有了服务网关可以提高微服务的安全性,网关校验请求的合法性,请求不合法将被拦截,拒绝访问。
Zuul与Nginx在实际项目中配合使用,Nginx的作用是反向代理、负载均衡,Zuul的作用是保障微服务的安全访问,拦截微服务请求,校验合法性及负载均衡。
二、使用示例
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>zuul:
routes:
manage‐course: #路由名称,名称任意,保持所有路由名称唯一
path: #访问路径匹配
serviceId: #指定服务id,从Eureka中找到服务的ip和端口
#url: http://localhost:8080 #也可指定url
strip‐prefix: false #true:代理转发时去掉前缀,false:代理转发时不去掉前缀
sensitiveHeaders: #默认zuul会屏蔽cookie,cookie不会传到下游服务,这里设置为空则取消默认的黑名单,如果设置了具体的头信息则不会传到下游服务
# ignoredHeaders: Authorization 可以设置过虑的头信息,默认为空表示不过虑任何头
zuul.routes.<route>.path=/xxx/** : 来指定映射路径。 <route> 是自定义的路由名
zuul.routes.<route>.serviceId=/product-service :来指定服务名
可以简化为一条 zuul: routes: shop-service-product: /product-service/**
zuul会根据product-service服务名默认映射到/product-service/**启动类添加@EnableZuulProxy // 开启Zuul的网关功能
三、过滤器
Zuul它包含了两个核心功能:对请求的路由和过滤。其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
过滤器可以说是Zuul实现API网关功能最为核心的部件,每一个进入Zuul的HTTP请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。
Zuul 中的过滤器跟 javax.servlet.Filter 不一样,javax.servlet.Filter 只有一种类型,可以通过配置 urlPatterns 来拦截对应的请求。而 Zuul 中的过滤器总共有 4 种类型,且每种类型都有对应的使用场景。
- PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请 求的微服务、记录调试信息等。
- ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient或Netfilx Ribbon请求微服务。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发生错误时执行该过滤器。
zuul中RequestContext对象
RequestContext:用于在过滤器之间传递消息。它的数据保存在每个请求的ThreadLocal中。它用于存储请求路由到哪里、错误、HttpServletRequest、HttpServletResponse都存储在RequestContext中。RequestContext扩展了ConcurrentHashMap,所以,任数据都可以存储在上下文中
// 自定义过虑器需要继承 ZuulFilter,ZuulFilter是一个抽象类,重写四个方法
public abstract ZuulFilter implements IZuulFilter{
//返回字符串,代表过滤器的类型。包含以下4种
//pre:请求在被路由之前执行
//route/routing:在路由请求时调用
//post:在route和errror过滤器之后调用
//error:处理请求时发生错误调用
abstract public String filterType();
//过返回的int值来定义过滤器的执行顺序,数字越小优先级越高
abstract public int filterOrder();
//判断该过滤器是否需要执行。返回true执行,返回false不执行。
boolean shouldFilter();// 来自IZuulFilter
//过滤器的具体业务逻辑。
Object run() throws ZuulException;// IZuulFilter
}正常流程
请求到达首先会经过pre类型过滤器,而后到达route类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
异常流程
整个过程中,pre或者route过滤器出现异常,都会直接进入error过滤器,在error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
如果是error过滤器自己出现异常,最终也会进入POST过滤器,将最终结果返回给请求客户端。
如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和route不同的是,请求不会再到达POST过滤器了。
使用场景
请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
异常处理:一般会在error类型和post类型过滤器中结合来处理。
服务调用时长统计:pre和post结合使用。
四、流程分析
在Zuul中, 整个请求的过程是这样的,首先将请求给zuulservlet处理,zuulservlet中有一个zuulRunner对象,该对象中初始化了RequestContext:作为存储整个请求的一些数据,并被所有的zuulfilter共享。zuulRunner中还有 FilterProcessor,FilterProcessor作为执行所有的zuulfilter的管理器。FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载,并支持groovy热加载,采用了轮询的方式热加载。有了这些filter之后,zuulservelet首先执行的Pre类型的过滤器,再执行route类型的过滤器,最后执行的是post 类型的过滤器,如果在执行这些过滤器有错误的时候则会执行error类型的过滤器。执行完这些过滤器,最终将请求的结果返回给客户端。
初始化
根据自动装载原则可以在 spring- cloud-netflix-zuul-2.1.0.RELEASE.jar 下找到 spring.factories 加载了ZuulServerAutoConfiguration,ZuulProxyAutoConfiguration,是Zuul服务端的自动配置类。
ZuulProxyAutoConfiguration 继承了 ZuulServerAutoConfiguration,
ZuulServerAutoConfiguration配置类的作用:
CompositeRouteLocator:组合路由定位器,看入参就知道应该是会保存好多个RouteLocator,构造过程中其实仅包括一个DiscoveryClientRouteLocator。
SimpleRouteLocator:默认的路由定位器,主要负责维护配置文件中的路由配置。
ZuulController:Zuul创建的一个Controller,用于将请求交由ZuulServlet处理。
ZuulHandlerMapping:这个会添加到SpringMvc的HandlerMapping链中,只有选择了ZuulHandlerMapping的请求才能出发到Zuul的后续流程。
注册ZuulFilterInitializer,通过FilterLoader加载应用中所有的过滤器并将过滤器注册到FilterRegistry,
请求转发
ZuulHandlerMapping ,为SpringMVC中 HandlerMapping 的拓展实现,会自动的添加到HandlerMapping链中。
主要目的就是把所有路径的请求导入到ZuulController上.另外的功效是当觉察RouteLocator路由表变更,则更新自己dirty状态,重新注册所有Route到ZuulController
在 ZuulController 中的 handleRequest 方法,会调用已经注册的 ZuulServlet 完成业务请求。主要逻辑在ZuulServlet的service中执行。
过滤器
Zuul默认注入的过滤器可以在 spring-cloud-netflix-core.jar 中找到。
| 过滤器 | order | 描述 | 类型 |
|---|---|---|---|
| ServletDetectionFilter | -3 | 检测请求是用DispatcherServlet还是ZuulServlet | pre |
| Servlet30WrapperFilter | -2 | 在Servlet 3.0 下,包装requests | pre |
| FormBodyWrapperFilter | -1 | 解析表单数据 | pre |
| SendErrorFilter | 0 | 如果中途出现错误 | error |
| DebugFilter | 1 | 设置请求过程是否开启debug | pre |
| PreDecorationFilter | 5 | 根据uri决定调用哪一个route过滤器 | pre |
| RibbonRoutingFilter | 10 | 如果写配置的时候用ServiceId则用这个route过滤器,该过滤器可以用Ribbon做负载均衡,用hystrix做熔断 | route |
| SimpleHostRoutingFilter | 100 | 如果写配置的时候用url则用这个route过滤 | route |
| SendForwardFilter | 500 | 用RequestDispatcher请求转发 | route |
| SendResponseFilter | 1000 | 用RequestDispatcher请求转发 | post |
五、zuul存在问题
性能问题
Zuul1x版本本质上就是一个同步Servlet,采用多线程阻塞模型进行请求转发。简单讲,每来一个请求,Servlet容器要为该请求分配一个线程专门负责处理这个请求,直到响应返回客户端这个线程才会被释放返回容器线程池。如果后台服务调用比较耗时,那么这个线程就会被阻塞,阻塞期间线程资源被占用,不能干其它事情。我们知道Servlet容器线程池的大小是有限制的,当前端请求量大,而后台慢服务比较多时,很容易耗尽容器线程池内的线程,造成 容器无法接受新的请求。
不支持任何长连接,如websocket